package edu.pku.sei.modeler.handler;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.Border;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Cursors;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Locator;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.ToolbarLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.text.FlowPage;
import org.eclipse.draw2d.text.TextFlow;
import org.eclipse.gef.DragTracker;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Handle;
import org.eclipse.gef.handles.AbstractHandle;
import org.eclipse.gef.palette.CreationToolEntry;
import org.eclipse.gef.palette.PaletteDrawer;
import org.eclipse.gef.palette.PaletteEntry;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;

/**
 * 
 * 
 * @author dumoulin
 */
public class ActionHandleContainer extends AbstractHandle implements Handle {

	/**
	 * 
	 */
	protected static final int channel = 22;

	/**
	 * 
	 */
	public static final boolean HORIZONTAL = true;

	/**
	 * 
	 */
	public static final boolean VERTICAL = false;

	/**
	 * tool describing the handle.
	 */
	protected PaletteDrawer toolEntries;

	/**
	 * 
	 */
	private ActionHandleFactory handleFactory;

	/**
	 * 
	 */
	private static final Color lightBlue = ColorConstants.lightBlue;

	/**
	 * 
	 */
	private static final Border TOOLTIP_BORDER = new MarginBorder(0, 2, 1, 0);

	/**
	 * 
	 */
	private static ImageCache globalImageCache;

	/**
	 * Create a ActionHandle.
	 * 
	 * @param locator
	 *            The locator used to locate the handle on the editPart.
	 * @param owner
	 * @param handleFactory
	 * @param tool
	 *            The tool used to configure the handle.
	 * @param toolEntry
	 */
	public ActionHandleContainer(GraphicalEditPart owner,
			PaletteDrawer toolEntry, Locator locator,
			ActionHandleFactory handleFactory) {

		this(owner, toolEntry, locator, handleFactory, ToolbarLayout.VERTICAL);
	}

	/**
	 * Create a ActionHandle.
	 * 
	 * @param isHorizontal
	 * @param locator
	 *            The locator used to locate the handle on the editPart.
	 * @param owner
	 * @param handleFactory
	 * @param tool
	 *            The tool used to configure the handle.
	 * @param toolEntry
	 */
	public ActionHandleContainer(GraphicalEditPart owner,
			PaletteDrawer toolEntry, Locator locator,
			ActionHandleFactory handleFactory, boolean isHorizontal) {
		super(owner, locator, Cursors.CROSS);

		// if (owner instanceof TemplateSignatureEditPart) {
		// setDragTracker(new
		// TemplateSignatureTracker((TemplateSignatureEditPart)owner));
		// }
		// else if (owner instanceof RedefinableTemplateSignatureEditPart) {
		// setDragTracker(new
		// RedefinableTemplateSignatureTracker((RedefinableTemplateSignatureEditPart)owner))
		// ;
		// }

		this.toolEntries = toolEntry;
		this.handleFactory = handleFactory;
		// next is required to see something :-)
		// setPreferredSize(new Dimension(DEFAULT_HANDLE_SIZE,
		// DEFAULT_HANDLE_SIZE));
		// setPreferredSize(new Dimension(getSmallImage()));
		Dimension parentSize = owner.getFigure().getSize();
		// container init
		ToolbarLayout layout = new ToolbarLayout(isHorizontal);
		layout.setStretchMinorAxis(true);
		layout.setMinorAlignment(ToolbarLayout.ALIGN_TOPLEFT);
		int spacing = 2;
		layout.setSpacing(spacing);

		setLayoutManager(layout);
		Border border;
		// border = new MarginBorder(3);
		border = new MarginBorder(parentSize.height / 2, 5, parentSize.height,
				5);
		setBorder(border);

		// Add entries
		Iterator<CreationToolEntry> handlesIter = getEntryChildren().iterator();
		while (handlesIter.hasNext()) {
			CreationToolEntry entry = handlesIter.next();
			// Debug.debug(this, "add "+entry, channel);
			add(createHandleFor(owner, entry, new NoOpLocator(), isHorizontal));
		}
	}

	/**
	 * Return the children of this entry.
	 * 
	 * @return
	 */
	protected List getEntryChildren() {
		return toolEntries.getChildren();
	}

	/**
	 * Create the Handle for the specified paletteEntry. Default implementation
	 * call the factory.
	 * 
	 * @param isHorizontal
	 * @param locator
	 * @param owner
	 * @param paletteEntry
	 * 
	 * @return
	 */
	protected AbstractHandle createHandleFor(GraphicalEditPart owner,
			PaletteEntry paletteEntry, Locator locator, boolean isHorizontal) {
		return handleFactory
				.createActionHandleFor(owner, paletteEntry, locator);
	}

	/**
	 * Paint the handle.
	 * 
	 * @param g
	 */
	@Override
	public void paintFigure(Graphics g) {
		// Relocate the handle, so that it is paint at its correct place.
		getLocator().relocate(this);
		super.paintFigure(g);
	}

	/**
	 * 
	 * 
	 * @return
	 */
	@Override
	public IFigure getToolTip() {
		return createToolTip();
	}

	/**
	 * Create the tooltip figure.
	 * 
	 * @return
	 */
	protected IFigure createToolTip() {
		String message = getToolTipText();
		if ((message == null) || (message.length() == 0)) {
			return null;
		}

		FlowPage fp = new FlowPage() {
			@Override
			public Dimension getPreferredSize(int w, int h) {
				Dimension d = super.getPreferredSize(-1, -1);
				if (d.width > 150) {
					d = super.getPreferredSize(150, -1);
				}
				return d;
			}
		};
		fp.setOpaque(true);
		fp.setBorder(TOOLTIP_BORDER);
		TextFlow tf = new TextFlow();
		tf.setText(message);
		fp.add(tf);
		return fp;
	}

	/**
	 * Get the text shown in the tooltip.
	 * 
	 * @return
	 */
	protected String getToolTipText() {
		String text = "tooltip to be implemented";
		return text;
	}

	/**
	 * Do we want name in tooltip ? Default return false.
	 * 
	 * @return
	 */
	protected boolean nameNeededInToolTip() {
		return false;
	}

	/**
	 * 
	 * 
	 * @return
	 */
	protected Color getBorderColor() {
		return ColorConstants.black;
	}

	/**
	 * 
	 * 
	 * @return
	 */
	protected Color getLineColor() {
		return ColorConstants.blue;
	}

	/**
	 * 
	 * 
	 * @return
	 */
	protected Color getFillColor() {
		return lightBlue;
	}

	/**
	 * Get the small image
	 * 
	 * @return
	 */
	// private Image getSmallImage() {
	// if (smallImage == null) { // Create the image
	// smallImage = createImage(toolEntries.getSmallIcon());
	// }
	//
	// return smallImage;
	// }
	/**
	 * Create an image from its name.
	 * 
	 * @param descriptor
	 * 
	 * @return
	 */
	protected Image createImage(ImageDescriptor descriptor) {
		return getImageCache().getImage(descriptor);
	}

	/**
	 * Creates a new drag tracker suitable for the toolEntry.
	 * 
	 * @return a new drag tracker
	 */
	@Override
	public DragTracker createDragTracker() {
		// No drag tracker, as this is a collection of figures.
		return null;
		// CreationFactory factory = (CreationFactory) toolEntries
		// .getToolProperty(CreationTool.PROPERTY_CREATION_FACTORY);
		// if (toolEntries instanceof ConnectionCreationToolEntry)
		// return new ConnectionDragCreationTool(factory);
		// else if (toolEntries instanceof CreationToolEntry)
		// return new CreationDragTool(factory, getOwner());
		//
		// else
		// throw new UnsupportedOperationException(
		// "No DragTracker defined for '" + toolEntries + "'.");
	}

	/**
	 * Get the editpart to which this handle is associated.
	 * 
	 * @return
	 */
	@Override
	public GraphicalEditPart getOwner() {
		return super.getOwner();
	}

	/**
	 * Returns the image cache. The cache is global, and is shared by all
	 * palette edit parts. This has the disadvantage that once an image is
	 * allocated, it is never freed until the display is disposed. However, it
	 * has the advantage that the same image in different palettes is only ever
	 * created once.
	 * 
	 * @return
	 */
	protected static ImageCache getImageCache() {
		ImageCache cache = globalImageCache;
		if (cache == null) {
			globalImageCache = cache = new ImageCache();
			Display display = Display.getDefault();
			if (display != null) {
				display.disposeExec(new Runnable() {
					public void run() {
						if (globalImageCache != null) {
							globalImageCache.dispose();
							globalImageCache = null;
						}
					}
				});
			}
		}
		return cache;
	}

	/**
	 * Inner class.
	 * 
	 * @author dumoulin
	 */
	protected static class ImageCache {

		/**
		 * Map from ImageDescriptor to Image.
		 */
		private Map images = new HashMap(11);

		/**
		 * 
		 * 
		 * @param desc
		 * 
		 * @return
		 */
		Image getImage(ImageDescriptor desc) {
			if (desc == null) {
				return null;
			}
			Image img = null;
			Object obj = images.get(desc);
			if (obj != null) {
				img = (Image) obj;
			} else {
				img = desc.createImage();
				images.put(desc, img);
			}
			return img;
		}

		/**
		 * 
		 * 
		 * @return
		 */
		Image getMissingImage() {
			return getImage(ImageDescriptor.getMissingImageDescriptor());
		}

		/**
		 * 
		 */
		void dispose() {
			for (Iterator i = images.values().iterator(); i.hasNext();) {
				Image img = (Image) i.next();
				img.dispose();
			}
			images.clear();
		}
	}

	/**
	 * 
	 */
	public class NoOpLocator implements Locator {

		/**
		 * 
		 * 
		 * @param target
		 */
		public void relocate(IFigure target) {
			// TODO Auto-generated method stub

		}

	}

	/**
	 * 
	 * 
	 * @return
	 */
	public ActionHandleFactory getHandleFactory() {
		return handleFactory;
	}

	/**
	 * 
	 * 
	 * @param handleFactory
	 */
	public void setHandleFactory(ActionHandleFactory handleFactory) {
		this.handleFactory = handleFactory;
	}

}
